Skip to content

Replace AnyHashable#772

Merged
mickael-menu merged 14 commits into
readium:swift6from
stevenzeck:refactor/anyhashable
May 29, 2026
Merged

Replace AnyHashable#772
mickael-menu merged 14 commits into
readium:swift6from
stevenzeck:refactor/anyhashable

Conversation

@stevenzeck
Copy link
Copy Markdown
Contributor

@stevenzeck stevenzeck commented Apr 11, 2026

AnyHashable does not conform with Sendable. Replaced usages of it, including the introduction AnySendableHashable: A new package private type-erased wrapper for values that conform to both Hashable and Sendable. It includes an .unwrap(as:) function for safe, generic extraction of the underlying value.

Public API Breaking Changes

  1. Pointer, TouchPointer, MousePointer: The id property type was changed from AnyHashable to PointerId.
  2. InputObservableToken: The id property type was changed from AnyHashable to UUID.
  3. Decoration: The userInfo dictionary was changed from [AnyHashable: AnyHashable] to [String: AnyHashable].
  4. ContentAttributesHolder: The generic accessors (subscript<T>, attribute<T>, attributes<T>) were updated to restrict the generic parameter T to Hashable & Sendable.

Related to #758.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR moves several public APIs away from AnyHashable toward Sendable-compatible identifiers and a new type-erased AnySendableHashable, supporting the broader Swift 6 strict concurrency effort.

Changes:

  • Introduces AnySendableHashable and applies it to decoration, HTTP request, and content attribute metadata.
  • Replaces pointer and input observer token identifiers with Sendable-specific types.
  • Updates navigator/test app/docs call sites to unwrap type-erased config values.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
TestApp/Sources/Reader/Common/VisualReaderViewController.swift Updates page-list decoration config extraction to use unwrap(as:).
Sources/Shared/Toolkit/HTTP/HTTPRequest.swift Makes HTTPRequest Sendable and updates userInfo typing.
Sources/Shared/Toolkit/AnySendableHashable.swift Adds the new Sendable Hashable type-erasure wrapper.
Sources/Shared/Publication/Services/Content/Content.swift Updates content attributes to store Sendable hashable values.
Sources/Navigator/PDF/PDFNavigatorViewController.swift Wraps gesture object identifiers in PointerId.
Sources/Navigator/Input/Pointer/PointerEvent.swift Makes pointer event types Sendable and introduces PointerId.
Sources/Navigator/Input/Pointer/DragPointerObserver.swift Updates drag observer state to store PointerId.
Sources/Navigator/Input/Pointer/ActivatePointerObserver.swift Updates activation observer state to store PointerId.
Sources/Navigator/Input/InputObservingGestureRecognizerAdapter.swift Updates pending pointer tracking to key by PointerId.
Sources/Navigator/Input/InputObservableViewController.swift Creates pointer IDs with the new PointerId enum.
Sources/Navigator/Input/InputObservable.swift Changes input observable token IDs to UUID and marks tokens Sendable.
Sources/Navigator/EPUB/HTMLDecorationTemplate.swift Updates highlight config extraction to use unwrap(as:).
Sources/Navigator/EPUB/EPUBSpreadView.swift Converts JavaScript pointer IDs into PointerId.int.
Sources/Navigator/Decorator/DecorableNavigator.swift Updates decoration metadata/config to use AnySendableHashable and marks decoration types Sendable.
docs/Guides/Navigator/Decorations.md Updates decoration guide sample to use unwrap(as:).
Comments suppressed due to low confidence (2)

Sources/Navigator/Decorator/DecorableNavigator.swift:134

  • When callers already have an AnySendableHashable and pass it as config, this generic initializer wraps it again, so later style.config?.unwrap(as: OriginalConfig.self) returns nil because the stored base is the wrapper rather than the original value. Since config is exposed as AnySendableHashable?, this needs an overload or special handling to avoid nested wrappers.
        public init<T: Hashable & Sendable>(id: Id, config: T? = nil) {
            self.id = id
            self.config = config.map(AnySendableHashable.init)

Sources/Shared/Toolkit/AnySendableHashable.swift:22

  • The equality closure compares using only the left-hand side's stored type, which can make equality asymmetric for class hierarchies (for example a wrapped superclass value may consider a wrapped subclass value equal, while the reverse comparison fails). Hashable requires symmetric equality, so this can lead to incorrect Set/Dictionary behavior for AnySendableHashable values.
    public static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.equals(rhs.base)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/Navigator/Input/Pointer/PointerEvent.swift
Comment thread Sources/Navigator/Decorator/DecorableNavigator.swift
Comment thread Sources/Shared/Toolkit/AnySendableHashable.swift Outdated
Comment thread docs/Guides/Navigator/Decorations.md Outdated
Comment thread docs/Guides/Navigator/Decorations.md Outdated
Comment thread Sources/Navigator/Decorator/DecorableNavigator.swift Outdated
Comment thread Sources/Shared/Publication/Services/Content/Content.swift Outdated
Comment thread Sources/Shared/Toolkit/AnySendableHashable.swift Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comment thread Tests/SharedTests/Toolkit/AnySendableHashableTests.swift
Comment thread docs/Guides/Navigator/Decorations.md
Comment thread Sources/Navigator/Decorator/DecorableNavigator.swift
Copy link
Copy Markdown
Member

@mickael-menu mickael-menu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @stevenzeck!

@mickael-menu mickael-menu merged commit bddac54 into readium:swift6 May 29, 2026
5 checks passed
@stevenzeck stevenzeck deleted the refactor/anyhashable branch June 1, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants